/*
 * Copyright © 2009 Perseus Project - Tufts University <http://www.perseus.tufts.edu>
 *
 * This file is part of UniCollatorPerseus.
 *
 * OcroPerseus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * OcroPerseus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with OcroPerseus.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.himeros.ocr.hocr;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import eu.himeros.transcoder.TransHandler;
import eu.himeros.util.IoManager;

import java.util.Hashtable;
import java.util.Vector;

/**
 * Parse the Ocropus html output to attribute coordinates not line by line, but word by word.
 *
 * @author Federico Boschetti <federico.boschetti.73@gmail.com>
 */
public class OcrHtml2WordHtml extends TransHandler {
    Stack<String> stk = new Stack<String>();
    XMLReader xr = null;
    boolean inSpan = false;
    boolean sameSeq = false;
    String line = "";
    Vector<Vector> lines = new Vector<Vector>(100);
    Vector<String[]> charCoords = new Vector<String[]>(100);
    int lid = 0; //line id
    int wid = 0; //word id
    BufferedWriter wordBw = null;
    static String fileName = null;
    Vector<String> wordV = new Vector<String>(1000);
    Hashtable<String, Integer> freqHt = new Hashtable<String, Integer>(1000);
    int wyMin = Integer.MAX_VALUE;
    int wyMax = Integer.MIN_VALUE;

    /**
     * Default Constructor.
     */
    public OcrHtml2WordHtml() {
        super();
        wordBw = IoManager.getWriter(fileName + ".csv");
    }

    /**
     * Parse the file.
     *
     * @param inFile input file.
     * @param outFile output file.
     */
    public void parse(String inFile, String outFile) {
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
            xr = XMLReaderFactory.createXMLReader();
            xr.setProperty("http://xml.org/sax/properties/lexical-handler", this);
            xr.setContentHandler(this);

            xr.setErrorHandler(this);
            xr.parse(inFile);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * StartDocument parsing method.
     */
    @Override
    public void startDocument() {
        try {
            bw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
                    "<head>" +
                    "<meta content=\"ocr_line ocr_page\" name=\"ocr-capabilities\"/>\n" +
                    "<meta content=\"en\" name=\"ocr-langs\"/>\n" +
                    "<meta content=\"Latn\" name=\"ocr-scripts\"/>\n" +
                    "<meta content=\"\" name=\"ocr-microformats\"/>\n" +
                    "<title>OCR Output</title></head>\n<body>");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * StartElement parsing method.
     *
     * @param uri
     * @param localName
     * @param qName
     * @param attributes
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        try {
            if (qName.equals("div") || qName.equals("p")) {
                if (qName.equals("div") || qName.equals("p")) {
                    bw.write("\n");
                }
                writeStartElement = true;
                super.startElement(uri, localName, qName, attributes);
                writeStartElement = false;
            } else if (qName.equals("span")) {
                String titleVal = attributes.getValue("title");
                String bboxes = null;
                if (titleVal != null) {
                    bboxes = titleVal.substring(titleVal.lastIndexOf("bboxes") + 7);
                    String[] bboxesItems = bboxes.split(", ");
                    for (int i = 0; i < bboxesItems.length; i++) {
                        charCoords.add(bboxesItems[i].split(" "));
                    }
                }
                line = "";
                inSpan = true;
                bw.write("<div id=\"" + (lid++) + "\"");
                for (int i = 0; attributes != null && i < attributes.getLength(); i++) {
                    bw.write(" " + attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\"");
                }
                bw.write(">");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Characters parsing method.
     *
     * @param ch
     * @param start
     * @param length
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void characters(char ch[], int start, int length) throws SAXException {
        //String line=decode(ch);
        try {
            if (inSpan) {
                line += new String(ch, start, length);
            } else {
                bw.write(new String(ch, start, length));
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        sameSeq = true;
    }

    /**
     * EndElement parsing method.
     *
     * @param uri
     * @param localName
     * @param qName
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        try {
            if (qName.equals("div") || qName.equals("p") || qName.equals("span")) {
                if (qName.equals("div") || qName.equals("p")) {
                    bw.write("\n");
                }
                if (qName.equals("span")) {
                    inSpan = false;
                    if (line.charAt(line.length() - 1) == '\n') {
                        line = line.substring(0, line.length() - 1);
                    }
                    String word = "";
                    String wx1 = charCoords.get(0)[0];
                    String wy1 = charCoords.get(0)[1];
                    String wx2 = "";
                    String wy2 = "";
                    for (int i = 0; i < line.length(); i++) {
                        if (line.charAt(i) == ' ' || i == line.length() - 1) {
                            if (i == line.length() - 1) {
                                word += line.charAt(i);
                            }
                            bw.write("\n<span id=\"" + (wid++) + "\" class=\"ocr_word\" coords=\"bbox " + wx1 + " " + wy1 + " " + wx2 + " " + wy2);
                            bw.write("\">" + word + "</span> ");
                            wordV.add(word);
                            Integer freqInt = freqHt.get(word);
                            if (freqInt == null) {
                                freqHt.put(word, new Integer(1));
                            } else {
                                freqHt.put(word, new Integer(freqInt.intValue() + 1));
                            }
                            word = "";
                            if (i < line.length() - 1) {
                                wx1 = charCoords.get(i + 1)[0];
                                wy1 = charCoords.get(i + 1)[1];
                                int wy1Int = Integer.parseInt(wy1);
                                wyMin = Math.min(wy1Int, wyMin);
                                wy1 = "" + wyMin;
                            }
                        } else {
                            word += line.charAt(i);
                            wx2 = charCoords.get(i)[2];
                            wy2 = charCoords.get(i)[3];
                            int wy2Int = Integer.parseInt(wy2);
                            wyMax = Math.max(wy2Int, wyMax);
                            wy2 = "" + wyMax;
                        }
                    }
                    charCoords.removeAllElements();
                    line = "";
                    wyMin = Integer.MAX_VALUE;
                    wyMax = Integer.MIN_VALUE;
                }
                if (qName.equals("span")) {
                    bw.write("</div><br/>\n");
                } else {
                    writeEndElement = true;
                    super.endElement(uri, localName, qName);
                    writeEndElement = false;
                }
            }
            sameSeq = false;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * EndDocument parsing method.
     */
    @Override
    public void endDocument() {
        try {
            bw.write("</body>\n</html>");
            int i = 0;
            for (String w : wordV) {
                wordBw.write("" + (++i) + "\t" + freqHt.get(w).toString() + "\t" + w);
                wordBw.newLine();
            }
            bw.close();
            wordBw.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * The <code>main</code> method.
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        if (args == null || args.length == 0) {
            System.out.println("Usage: eu.himeros.ocr.OcrHtml2DjvuXml <fileNameWithoutExtension>");
        }
        fileName = args[0];
        OcrHtml2WordHtml oh = new OcrHtml2WordHtml();

        oh.parse(fileName + ".html", fileName + "-x.html");
    }
}
